package analisis.enrichAnalysis;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import kegg.Pathway;
import keggacces.Descarga;

/**
 * @file EnrichAnalysisKegg.java
 * @author Juan Humanes Ferrer
 * @date 04-Marzo-2014
 *
 */
public final class EnrichAnalysisKegg implements IEnrichAnalysis {

    //Atributos de entrada
    private Double q; //Numero de genes de la lista de genes de entrada
    private List<Double> listaN; //Numero de genes de cada Pathway
    private List<Double> listaM; //Numero de la lista de genes que estan en el pathway
    private Double total; //Numero total de genes de un organismo
    private final List<Pathway> pathways; //Nombre de los pathways

    private String sesion; //Sesion del usuario

    //Atributos de formulas
    private List<Double> pValues; //Valor p-value
    private List<Double> bonferronis; //Corrección bonferoonis
    private List<Double> bonferroniStepDown; //Corrección bonferoonisStepDown

    /**
     * Constructor que hay que pasarle la ruta donde esta el "organismo".txt, el
     * organismo y la ruta del fichero de entrada (una lista de genes). Se
     * calcula los atributos de entrada(q, n, m y total) Y a partir de estos se
     * calcula el p-value sobre cada pathway y las correcciones.
     *
     * @param sesionId ruta donde se encuentra la carpeta temporal del usuario
     * @param org organismo
     * @throws java.io.IOException
     */
    public EnrichAnalysisKegg(String sesionId, String org) throws IOException {

        this.pathways = Descarga.descargaOrganismoEnrich(org);
        this.sesion = sesionId;
        this.listaM = new ArrayList();
        this.listaN = new ArrayList();

        this.calculaQ();

        this.calculaN();

        this.calculaM();

        this.calculaTotal();

        this.pValues = new ArrayList();
        for (int i = 0; i < this.pathways.size(); i++) {
            Double ni = this.listaN.get(i);
            Double mi = this.listaM.get(i);
            Double pvaluei = calculaPValue(getQ(), ni, mi, getTotal());
            pValues.add(pvaluei);
        }

        //Se crea un objeto ICorreccion de tipo BonferroniCorrection
        ICorreccion bonferroniCorrection = new BonferroniCorrection();
        this.bonferronis = bonferroniCorrection.correction(getpValues(), getpValues().size());

        //Se crea un objeto ICorreccion de tipo BonferroniStepDownCorrection
        ICorreccion bonferroniStepDownCorrection = new BonferroniStepDownCorrection();
        this.bonferroniStepDown = bonferroniStepDownCorrection.correction(getpValues(), getpValues().size());

    }

    /**
     * Método que retorna la lista de correcciones Bonferroni
     *
     * @return bonferronis
     */
    @Override
    public List<Double> getBonferronis() {
        return bonferronis;
    }

    /**
     * Método que modifica la lista de correcciones Bonferroni
     *
     * @param bonferronis
     */
    @Override
    public void setBonferronis(List<Double> bonferronis) {
        this.bonferronis = bonferronis;
    }

    /**
     * Método que retorna la lista de correcciones BonferroniStepDown
     *
     * @return bonferronisStepDown
     */
    @Override
    public List<Double> getBonferroniStepDown() {
        return bonferroniStepDown;
    }

    /**
     * Método que modifica la lista de correcciones BonferroniStepDown
     *
     * @param bonferroniStepDown
     */
    @Override
    public void setBonferroniStepDown(List<Double> bonferroniStepDown) {
        this.bonferroniStepDown = bonferroniStepDown;
    }

    /**
     * Método que retorna el atributo q, que es el numero de genes de la lista
     * de genes de entrada
     *
     * @return q
     */
    public Double getQ() {
        return q;
    }

    /**
     * Método que modifica el atributo q.
     *
     * @param q
     */
    public void setQ(Double q) {
        this.q = q;
    }

    /**
     * Método que retorna el atributo listaN, que es la lista del numero de
     * genes de cada Pathway
     *
     * @return n
     */
    public List<Double> getListaN() {
        return listaN;
    }

    /**
     * Método que modifica el atributo listaN.
     *
     * @param listaN
     */
    public void setListaN(List<Double> listaN) {
        this.listaN = listaN;
    }

    /**
     * Método que retorna el atributo listaN, que es el la lista de
     * coincidencias, es decir, el numero de genes la lista de genes entrada que
     * estan en el pathway.
     *
     * @return listaM
     */
    public List<Double> getListaM() {
        return listaM;
    }

    /**
     * Método que modifica el atributo listaM.
     *
     * @param listaM
     */
    public void setListaM(List<Double> listaM) {
        this.listaM = listaM;
    }

    /**
     * Método que retorna el atributo total, que es el numero total de genes de
     * un organismo
     *
     * @return total
     */
    public Double getTotal() {
        return total;
    }

    /**
     * Método que modifica el atributo total.
     *
     * @param total
     */
    public void setTotal(Double total) {
        this.total = total;
    }

    /**
     * Método que retorna el atributo pValues, que es la lista de valores
     * p-value sobre cada pathway
     *
     * @return pvalues
     */
    @Override
    public List<Double> getpValues() {
        return pValues;
    }

    /**
     * Método que modifica el atributo pValues.
     *
     * @param pValues
     */
    @Override
    public void setpValues(List<Double> pValues) {
        this.pValues = pValues;
    }

    /**
     * Método que retorna el atributo ruta, que es la Ruta donde se encuentran
     * los ficheros (.txt) con los genes de cada pathway
     *
     * @return ruta
     */
    /**
     * Método que retorna el atributo sesion, que es la Ruta donde se encuentra
     * el fichero (.txt) con la lista de genes del usuario
     *
     * @return sesion
     */
    @Override
    public String getSesion() {
        return sesion;
    }

    @Override
    public void setSesion(String sesion) {
        this.sesion = sesion;
    }

    /**
     * Método que retorna el atributo pathways, que es la lista de nombres de
     * los pathways
     *
     * @return pathways
     */
    public List<Pathway> getPathways() {
        return pathways;
    }

    /**
     * Método que calcula el número de genes de un fichero (.txt). Se pasa por
     * parámetro la ruta donde se encuentra el fichero.
     *
     * @param ruta
     * @return ngenes
     */
    private Double calculanNumeroGenes(String ruta) {
        File setEntrada = new File(ruta);
        Double ngenes = 0.0;
        FileReader fr = null;
        BufferedReader br = null;

        try {

            fr = new FileReader(setEntrada);
            br = new BufferedReader(fr);

            // Lectura del fichero
            while (br.readLine() != null) {
                ngenes++;

            }
        } catch (IOException e) {
        } finally {
            // En el finally cerramos el fichero, para asegurarnos
            // que se cierra tanto si todo va bien como si salta 
            // una excepcion.
            try {
                if (null != fr) {
                    fr.close();
                }
            } catch (IOException e2) {
            }
        }
        return ngenes;

    }

    /**
     * Método que devuelve la lista de genes de un fichero (.txt). Se pasa por
     * parámetro la ruta donde se encuentra el fichero.
     *
     * @param ruta
     * @return ngenes
     */
    private List<String> listaGenes(File archivo) {
        List<String> genes = new ArrayList();
        FileReader fr = null;
        BufferedReader br = null;

        try {

            fr = new FileReader(archivo);
            br = new BufferedReader(fr);

            // Lectura del fichero
            String linea;
            while ((linea = br.readLine()) != null) {
                //Se añade el gen a la lista
                genes.add(linea);

            }
        } catch (IOException e) {
        } finally {
            // En el finally cerramos el fichero, para asegurarnos
            // que se cierra tanto si todo va bien como si salta 
            // una excepcion.
            try {
                if (null != fr) {
                    fr.close();
                }
            } catch (IOException e2) {
            }
        }
        return genes;

    }

    /**
     * Método que calcula el numero de genes del fichero de entradas que estan
     * en el pathway. Se pasa por parametro el nombre del pathway.
     *
     * @param pathway
     * @return coincidencias
     */
    private Double calculaCoinicidencias(Pathway pathway) {
        Double coincidencias = 0.0;
        File setEntrada = new File(this.sesion);
        List<String> genesEntrada = this.listaGenes(setEntrada);

        List<String> genesPathway = pathway.listaGenes();

        for (String genesEntrada1 : genesEntrada) {
            /*Si el gen del fichero de entrada se encuentra en la lista de genes
             del pathway, aumenta el numero de coincidencias*/
            if (genesPathway.contains(genesEntrada1)) {
                coincidencias++;
            }
        }
        return coincidencias;

    }

    /**
     * Método que calcula el numero de de genes de un fichero de entrada y se lo
     * asigna a la variable q.
     *
     */
    private void calculaQ() {
        Double ngenes = this.calculanNumeroGenes(this.getSesion());
        this.setQ(ngenes);
    }

    /**
     * Método que calcula el número de genes de cada pathway y lo va añadiendo a
     * listaN.
     *
     */
    private void calculaN() {
        for (int i = 0; i < this.getPathways().size(); i++) {
            Double ngenes = this.getPathways().get(i).listaGenes().size() + 0.0;
            getListaN().add(ngenes);

        }
    }

    /**
     * Método que calcula el número de coincidencias de genes de un fichero de
     * entrada con cada pathway y lo va añadiendo a listaM.
     *
     */
    private void calculaM() {
        for (int i = 0; i < this.getPathways().size(); i++) {
            Double ncoincidencias = this.calculaCoinicidencias(this.getPathways().get(i));
            getListaM().add(ncoincidencias);
        }
    }

    /**
     * Método que calcula el número de genes total en todos los pathways y lo
     * asigna al atributo total.
     *
     */
    private void calculaTotal() {
        List<String> genesT = new ArrayList();
        Double ntotal = 0.0;
        for (int i = 0; i < this.getPathways().size(); i++) {
            List<String> genes = this.getPathways().get(i).listaGenes();
            for (String gene : genes) {
                /*Aumenta el numero de genes, si el gen no esta aun en la lista 
                 de genes*/
                if (!genesT.contains(gene)) {
                    genesT.add(gene);
                    ntotal++;

                }
            }

        }
        this.setTotal(ntotal);

    }

    /**
     * Método que calcula el pvalue de un fichero de entrada sobre un pathway.
     *
     * If p(P) is sufficiently small, then the number of genes in Q having
     * Pathway P is considered to be statistically significantly larger than
     * what would be expected if Ho were true; we say that P is represented in
     * Q.
     *
     *
     * Se pasa por parametro:
     *
     * @param q Numero de genes del fichero de entrada
     * @param n Numero de genes del pathway
     * @param m Numero de genes coincidentes entre el fichero de entrada y el
     * pathway
     * @param N Numero total de genes de todos los pathways
     *
     * @return pValue or statistical significance. It implies that one is
     * determining whether or not an obtained difference in an experiment is due
     * to chance or is likely to be due to the presence of a genuine
     * experimental effectt.
     */
    public double calculaPValue(double q, double n, double m, double N) {
        double pvaluemas = 0;
        double pvaluemenos = 0;

        double min;
        if (q < n) {
            min = q;
        } else {
            min = n;
        }

        for (double i = m; i <= min; i++) {
            pvaluemas = pvaluemas + probabilityOfFrequency(q, n, i, N);
        }
        for (double i = 0; i <= m; i++) {
            pvaluemenos = pvaluemenos + probabilityOfFrequency(q, n, i, N);
        }

        Double pValue = Math.min(pvaluemas, pvaluemenos);
        return pValue;
    }

    private double probabilityOfFrequency(double q, double n, double m, double N) {
        double res;

        res = 0;
        for (int i = 1; i <= N; i++) {
            double logi = Math.log10(i);

            if (i <= q) {
                res += logi;
            }

            if (i <= (N - q)) {
                res += logi;
            }

            if (i <= n) {
                res += logi;
            }

            if (i <= (N - n)) {
                res += logi;
            }

            res -= logi;

            if (i <= m) {
                res -= logi;
            }

            if (i <= (n - m)) {
                res -= logi;
            }

            if (i <= (q - m)) {
                res -= logi;
            }

            if (i <= (N - q - n + m)) {
                res -= logi;
            }

        }

        return Math.pow(10, res);
    }

}
